package be.ac.ulb.mlg.utils;

/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2013 Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * 
 * @author Jean-Sebastien Lerat (Jean-Sebastien.Lerat@ulb.ac.be)
 * @version 1.00, 24/06/2013
 */

import static org.junit.Assert.*;

import java.util.Arrays;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import be.ac.ulb.mlg.utils.TestUtils.DataSize;
import be.ac.ulb.mlg.utils.measure.Euclidean;

public class DefaultRenormalizerTest {
	private DefaultRenormalizer normalizer;
	private final double[][] inputBefore;
	private double[][] inputAfter;
	
	private static final boolean WITH_NATIVE = true;
	private static final boolean WITHOUT_NATIVE = false;
	
	public DefaultRenormalizerTest(){
		inputBefore = TestUtils.generateData(DataSize.SMALL,1e2,0d);
	}
	
	@Before
	public void setUp() throws Exception {
		this.normalizer		= new DefaultRenormalizer();
	}

	@After
	public void tearDown() throws Exception {
	}
	@Test
	public void testCountGroups(){
		final String[] groupsNames	= {"a","b"};
		
		this.normalizer.setGroupsNames(inputBefore.length,groupsNames);
		assertTrue("Group count differs (must be "+groupsNames.length+")",groupsNames.length == normalizer.countGroups());
		
		this.normalizer.noGroups();
		
		final int middle	= inputBefore.length>>1;
		final int[]	groups	= new int[inputBefore.length];
		Arrays.fill(groups,middle,groups.length,1);
		this.normalizer.setGroups(groups, 2);
		assertTrue("Group count differs (must be 2)",groupsNames.length == normalizer.countGroups());
	}
	@Test
	public void testGroupOf(){
		final String[] groups = {"a","b"};		
		final int middle = inputBefore.length>>1;
		this.normalizer.setGroupsNames(inputBefore.length,groups);
		for(int i=0;i<middle;i++)
			this.normalizer.setGroup(i, groups[0]);
		for(int i=middle;i<inputBefore.length;i++)
			this.normalizer.setGroup(i, 1);
		for(int i=0;i<middle;i++)
			assertTrue("group id differs", normalizer.groupOf(i) == 0);
		for(int i=middle;i<inputBefore.length;i++)
			assertTrue("group id differs", normalizer.groupOf(i) == 1);
	}
	@Test
	public void testSetNormalizerSimple(){
		final double firstDivider	= 1.0;
		final double lastDivider	= 100.0;
		
		this.normalizer.setNormalizer(0,firstDivider);
		for(int i=1;i<inputBefore[0].length-1;i++)
			this.normalizer.setNormalizer(i,DefaultRenormalizer.SUM_NORMILIZER);
		this.normalizer.setNormalizer(inputBefore[0].length-1,lastDivider);
		
		testHelper();
		
		final double[] sum = new double[inputBefore[0].length];//auto fill with 0
		for(int i=1;i<inputBefore.length-1;i++){
			for(int j=0;j<sum.length;j++)
				sum[j]+=inputBefore[i][j];
		}
		sum[0]						= firstDivider;
		sum[inputBefore[0].length-1]= lastDivider;
		for(int i=0;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++){
				assertTrue("All data not divied by the correct sum", TestUtils.equals(inputBefore[i][j]/sum[j],inputAfter[i][j]));
			}
		}
	}
	@Test
	public void testSetNormalizerGroups(){
		final String[] groups = {"a","b"};		
		final int middle = inputBefore.length>>1;
		this.normalizer.setGroupsNames(inputBefore.length,groups);
		for(int i=0;i<middle;i++)
			this.normalizer.setGroup(i, groups[0]);
		for(int i=middle;i<inputBefore.length;i++)
			this.normalizer.setGroup(i, groups[1]);
		
		final double[][] divider = new double[groups.length][this.inputBefore[0].length];
		for(double[] sub:divider)
			Arrays.fill(sub,DefaultRenormalizer.SUM_NORMILIZER);//not required because SUM_NORMILIZER==0.0
		
		final double aFirst	= 1.0;
		final double aLast		= 100.0;
		final double bFirst	= aLast;
		final double bLast		= aFirst;
		
		divider[0][0]					= aFirst;
		divider[1][0]					= bFirst;
		divider[0][divider[0].length-1]	= aLast;
		divider[1][divider[1].length-1]	= bLast;
		
		for(int i=0;i<divider[0].length;i++)
			this.normalizer.setNormalizer(0, i, divider[0][i]);
		
		for(int i=0;i<divider[1].length;i++)
			this.normalizer.setNormalizer(groups[1], i, divider[1][i]);
		
		testHelper();
		
		final double[][] sum = new double[groups.length][inputBefore[0].length];//auto fill with 0
		for(int i=1;i<inputBefore.length-1;i++){
			int group = i<middle?0:1;
			for(int j=0;j<sum.length;j++)
				sum[group][j]+=inputBefore[i][j];
		}
		sum[0][0]				= aFirst;
		sum[1][0]				= bFirst;
		sum[0][sum[0].length-1]	= aLast;
		sum[1][sum[1].length-1]	= bLast;
		
		for(int i=0;i<inputBefore.length;i++){
			int group = i<middle?0:1;
			for(int j=0;j<sum.length;j++){
				assertTrue("All data not divied by the correct sum", TestUtils.equals(inputBefore[i][j]/sum[group][j],inputAfter[i][j]));
			}
		}
	}
	
	@Test
	public void testNative() throws Exception{
		if(!this.normalizer.hasNativeImplementation())
			assertTrue(true);
		if(!Measurer.hasNativeSupport())
			fail("No native support");
		double[][] output;

		{//testAddFeature
			this.setUp();
			testAddFeature();
			output = testMeasurer(WITHOUT_NATIVE);
			this.tearDown();
			assertTrue("testAddFeature",TestUtils.equals(output,testMeasurer(WITH_NATIVE)));
		}
		{//testSetGroups
			this.setUp();
			testSetGroups();
			output = testMeasurer(WITHOUT_NATIVE);
			this.tearDown();
			assertTrue("testSetGroups",TestUtils.equals(output,testMeasurer(WITH_NATIVE)));
		}
		{//testNormalizeInput
			this.setUp();
			testNormalizeInput();
			output = testMeasurer(WITHOUT_NATIVE);
			this.tearDown();
			assertTrue("testNormalizeInput",TestUtils.equals(output,testMeasurer(WITH_NATIVE)));
		}
		{//testResetNormalizer
			this.setUp();
			testResetNormalizer();
			output = testMeasurer(WITHOUT_NATIVE);
			this.tearDown();
			assertTrue("testResetNormalizer",TestUtils.equals(output,testMeasurer(WITH_NATIVE)));
		}
		{//testSetNormalizerSimple
			this.setUp();
			testSetNormalizerSimple();
			output = testMeasurer(WITHOUT_NATIVE);
			this.tearDown();
			assertTrue("testSetNormalizerSimple",TestUtils.equals(output,testMeasurer(WITH_NATIVE)));
		}
		{//testSetNormalizerGroups
			this.setUp();
			testSetNormalizerGroups();
			output = testMeasurer(WITHOUT_NATIVE);
			this.tearDown();
			assertTrue("testSetNormalizerGroups",TestUtils.equals(output,testMeasurer(WITH_NATIVE)));
		}
	}	
	@Test
	public void testSetGroupsNames(){
		final String[] groups = {"a","b"};//autofill with group 0
		this.normalizer.setGroupsNames(inputBefore.length, groups);
		this.normalizer.setGroup(0, groups[0]);
		for(int i=1;i<inputBefore.length;i++)
			this.normalizer.setGroup(i, groups[1]);
		testHelper();
		
		final double[] sum = new double[inputBefore[0].length];//auto fill with 0
		for(int i=1;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++)
				sum[j]+=inputBefore[i][j];
		}
		for(int i=1;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++){
				assertTrue("All data not divied by the correct sum", TestUtils.equals(inputBefore[i][j]/sum[j],inputAfter[i][j]));
			}
		}
		final double[][] expected = new double[1][sum.length];
		Arrays.fill(expected[0],1.0);
		assertTrue("The first row must be fill with 1 (one unique member)",TestUtils.equals(expected, new double[][]{inputAfter[0]}));
	}
	@Test
	public void testSetGroups(){
		final int[] groups = new int[inputBefore.length];//autofill with group 0
		this.normalizer.setGroups(groups,2);
		this.normalizer.setGroup(0,1);
		testHelper();
		
		final double[] sum = new double[inputBefore[0].length];//auto fill with 0
		for(int i=1;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++)
				sum[j]+=inputBefore[i][j];
		}
		for(int i=1;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++){
				assertTrue("All data not divied by the correct sum", TestUtils.equals(inputBefore[i][j]/sum[j],inputAfter[i][j]));
			}
		}
		final double[][] expected = new double[1][sum.length];
		Arrays.fill(expected[0],1.0);
		assertTrue("The first row must be fill with 1 (one unique member)",TestUtils.equals(expected, new double[][]{inputAfter[0]}));
	}
	@Test
	public void testNormalizeInput(){
		testHelper();
		checkSum();
	}
	@Test
	public void testAddFeature(){
		final int first	= 0;
		final int middle	= inputBefore.length>>1;
		final int last		= inputBefore.length-1;
		this.normalizer.addFeature(first);
		this.normalizer.addFeature(middle);
		this.normalizer.addFeature(last);
		
		testHelper();
		
		if(inputAfter.length > 4){
			//data must change (div by the sum of first/middle/last)
			assertFalse("Input array must change",TestUtils.equals(inputBefore,inputAfter));
			final double[] sum = new double[inputBefore[0].length];//auto fill with 0
			for(int i=0;i<inputBefore.length;i++){
				if(i != first && i != middle && i != last){
					for(int j=0;j<sum.length;j++)
						sum[j]+=inputBefore[i][j];
				}
			}
			for(int i=0;i<inputBefore.length;i++){
				if(i != first && i != middle && i != last){
					for(int j=0;j<sum.length;j++){
						assertTrue("All data not divied by the correct sum", TestUtils.equals(inputBefore[i][j]/sum[j],inputAfter[i][j]));
					}
				}
			}
		}else{
			//data must be preserved
			assertTrue("Input array cannot change due to the length of input",TestUtils.equals(inputBefore,inputAfter));
		}
	}
	@Test
	public void testCountFeatures(){
		testAddFeature();// 3 features
		assertEquals("Feature count differs, must be 3",3,this.normalizer.countFeatures());
	}
	@Test
	public void testNormalizeOutput(){
		final double[][] outputBefore	= TestUtils.generateData(DataSize.SMALL,1e2,0d);
		final Measure measure			= null;
		final double[][] inputAfter		= TestUtils.copy(inputBefore);
		final double[][] outputAfter	= TestUtils.copy(outputBefore);
		
		this.normalizer.normalizeOutput(inputAfter, outputAfter, measure);
		
		final boolean outputMustBePreserved = !this.normalizer.processOutput();
		
		assertTrue("Input array change and cannot change",TestUtils.equals(inputBefore,inputAfter));
		assertTrue("Output modification differ from processOutput()",TestUtils.equals(outputBefore,outputAfter)==outputMustBePreserved);
	}
	@Test
	public void testProcessInput(){
		assertTrue("Specify input processing",this.normalizer.processInput());
	}
	@Test
	public void testProcessOutput(){
		assertTrue("Specify output processing",!this.normalizer.processOutput());
	}
	@Test
	public void testIsUsingFeatures(){
		this.normalizer.setGroups(new int[2],2);
		this.normalizer.addFeature(0);
		assertTrue("using features("+true+")", this.normalizer.isUsingFeatures());
		this.normalizer.noFeatures();
		assertTrue("using features("+false+")",! this.normalizer.isUsingFeatures());
	}
	@Test
	public void testIsUsingGroups(){
		this.normalizer.setGroups(new int[2],2);
		assertTrue("using groups("+true+")", this.normalizer.isUsingGroups());
		this.normalizer.noGroups();
		assertTrue("using groups("+false+")", !this.normalizer.isUsingGroups());
	}
	@Test
	public void testResetNormalizer(){
		this.normalizer.setNormalizer(0, 1.0);
		
		this.normalizer.resetNormalizer();
		
		testHelper();
		checkSum();
	}
	@Test
	public void testNoGroups(){
		final int[] groups = {0,1};
		final String[] names = {"a","b"};
		this.normalizer.setGroupsNames(names.length,names);
		this.normalizer.setGroup(0,groups[0]);
		this.normalizer.setGroup(1,names[1]);
		
		this.normalizer.noGroups();
		
		testHelper();
		checkSum();
	}
	@Test
	public void testNoFeatures(){
		this.normalizer.addFeature(0);
		
		this.normalizer.noFeatures();
		
		assertEquals("",0,this.normalizer.countFeatures());
		
		testHelper();
		checkSum();
	}
	
	private void checkSum() {
		final double[] sum = new double[inputBefore[0].length];//auto fill with 0
		for(int i=0;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++)
				sum[j]+=inputBefore[i][j];
		}
		for(int i=0;i<inputBefore.length;i++){
			for(int j=0;j<sum.length;j++){
				assertTrue("All data not divied by the correct sum", TestUtils.equals(inputBefore[i][j]/sum[j],inputAfter[i][j]));
			}
		}
	}

	private double[][] testMeasurer(boolean useNative){
		Measurer measurer = new Measurer();
		measurer.setRenormalizer(this.normalizer);
		measurer.useNative(useNative);
		return measurer.measure(TestUtils.copy(this.inputBefore), new Measure[]{new Euclidean()}).values().iterator().next();
	}
	private void testHelper(){
		this.inputAfter = TestUtils.copy(this.inputBefore);
		this.inputAfter = this.normalizer.normalizeInput(this.inputAfter);
	}
}
